using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading;
using System.Reflection;
using Microsoft.VisualBasic;
using System.Globalization;
using System.Diagnostics;

namespace vXCopy
{

	class ProcessFileCopy : CmdLineSettingLib
	{
		internal FileToProcess fileToProcess = null;

		internal event EventHandler<FileToProcess> EventfileCreatedToProcess;
		internal event EventHandler<FileToProcess> EventfileToProcess;
		internal event EventHandler<FileToProcess> EventfileErrorToProcess;
		internal event EventHandler<FileToProcess> EventfileProcessStatusToProcess;
		internal event EventHandler<FileToProcess> FileCompleted;

		internal FileAttributes fileAttributes;

		bool bAppend = false;
		long nStartOffset = 0;
		Int32 nbyte = 0;
		string sStatus = "";
		int nRow = 0;
		internal Thread copyFileThread = null;
		int ThreadNumber = 0;
		bool bFileCompleted = false;

		readonly object stateLock = new object();

		//internal ProcessFileCopy()
		//{}

		internal ProcessFileCopy(FileToProcess FileToProcess, int ThreadNumber)
		{
			this.fileToProcess = FileToProcess;
			this.ThreadNumber = ThreadNumber;
		}

		internal void SetFileAttributes(bool bComplete)
		{
			FileInfo ofileFromInfo = null;
			FileInfo ofileDestInfo = null;

			try
			{
				ofileFromInfo = new FileInfo(fileToProcess.fromFile);
				ofileDestInfo = new FileInfo(fileToProcess.destFile);

				DateTime d1 = new DateTime(1980, 2, 1, 23, 59, 59);
				DateTime d2 = ofileFromInfo.CreationTimeUtc;
				DateTime d3 = ofileFromInfo.LastWriteTimeUtc;

				fileAttributes = File.GetAttributes(fileToProcess.fromFile);

				File.SetAttributes(fileToProcess.destFile, FileAttributes.Normal);

				if (bComplete)
				{
					d1 = ofileFromInfo.LastAccessTimeUtc;
				}

				File.SetLastAccessTimeUtc(ofileDestInfo.FullName, d1);
				File.SetCreationTimeUtc(ofileDestInfo.FullName, d2);
				File.SetLastWriteTimeUtc(ofileDestInfo.FullName, d3);


				if (bComplete)
				{
					File.SetAttributes(fileToProcess.destFile, fileAttributes);
				}
			}

			catch (Exception ex)
			{
				Debug.WriteLine("ERROR::ProcessFileCopy::SetFileAttributes: " + ex.Message);
			}
		}

		internal void StartCopy()
		{
			copyFileThread = new Thread(this.ProcessFile);
			copyFileThread.Name = new FileInfo(this.fileToProcess.fromFile).Name;
			copyFileThread.IsBackground = true;
			copyFileThread.Start();
		}

		internal void Kill()
		{
			try
			{
				if (copyFileThread.IsAlive)
					copyFileThread.Abort();
			}
			catch (Exception)
			{ }
			finally
			{
				copyFileThread = null;
			}
		}

		private int GetChunkSize(long FileSize)
		{
			int nres = 4096;

			if (FileSize >= 0 && FileSize < 1024)
				nres = (int)FileSize;

			if (FileSize >= 1024 && FileSize < 2048)
				nres = 1024;

			if (FileSize >= 10000 && FileSize < 100000)
				nres = 4096;

			if (FileSize >= 100000 && FileSize < 1000000)
				nres = 4096 * 2;

			if (FileSize >= 1000000)
				nres = 4096 * 4;

			//Debug.WriteLine("]]] Chunksize: " + nres.ToString());
			return nres;
		}

		internal void ProcessFile(object state)
		{
			FileStream fromFileStream = null;
			FileStream destFileStream = null;
			FileInfo ofileFromInfo = null;
			FileInfo ofileDestInfo = null;

			try
			{
				fromFileStream = new FileStream(fileToProcess.fromFile, FileMode.Open, FileAccess.Read, FileShare.Read);

				ofileFromInfo = new FileInfo(fileToProcess.fromFile);
				ofileDestInfo = new FileInfo(fileToProcess.destFile);
				UtilLibrary.CheckCreateFolder(fileToProcess.destFolder, false);

				//Set Destfile DateTime+Normal attributes 
				SetFileAttributes(false);

				// Check Files's status
				if (ofileDestInfo.Exists)
				{
					TimeSpan oFromFileLastWriteTime = new TimeSpan(ofileFromInfo.LastWriteTime.Ticks);
					TimeSpan oDestFileLastWriteTime = new TimeSpan(ofileDestInfo.LastWriteTime.Ticks);
					TimeSpan oFromFileCreationTime = new TimeSpan(ofileFromInfo.CreationTime.Ticks);
					TimeSpan oDestFileCreationTime = new TimeSpan(ofileDestInfo.CreationTime.Ticks);

					if (oDestFileLastWriteTime == oFromFileLastWriteTime)
					{
						if (oFromFileCreationTime == oDestFileCreationTime && ofileFromInfo.Length == ofileDestInfo.Length)
						{
							sStatus = "Same version";
							bAppend = true;
							//
							destFileStream = new FileStream(fileToProcess.destFile, FileMode.Append, FileAccess.Write);

							nStartOffset = (long)destFileStream.Length;
							fromFileStream.Seek(ofileDestInfo.Length, 0);
							destFileStream.Seek(ofileDestInfo.Length, 0);
							//
							fileToProcess.ItemNumber = nRow;
							fileToProcess.ThreadID = Thread.CurrentThread.GetHashCode();
							fileToProcess.ItemStatus = sStatus;

							if (EventfileToProcess != null)
								EventfileToProcess(this, fileToProcess);

						}
						else
						{
							if (oFromFileCreationTime == oDestFileCreationTime && ofileFromInfo.Length > ofileDestInfo.Length)
							{
								sStatus = "Same Version (resume)";
								bAppend = true;
								//

								destFileStream = new FileStream(fileToProcess.destFile, FileMode.Append, FileAccess.Write);
								nStartOffset = (int)destFileStream.Length;
								fromFileStream.Seek(ofileDestInfo.Length, 0);
								destFileStream.Seek(ofileDestInfo.Length, 0);
								//
								fileToProcess.ItemNumber = nRow;
								fileToProcess.ThreadID = Thread.CurrentThread.GetHashCode();
								fileToProcess.ItemStatus = sStatus;

								if (EventfileToProcess != null)
									EventfileToProcess(this, fileToProcess);
							}

							else
							{
								sStatus = "New Version";
								ofileDestInfo = null;
								destFileStream = new FileStream(fileToProcess.destFile, FileMode.Create, FileAccess.Write);
								//
								fileToProcess.ItemNumber = nRow;
								fileToProcess.ThreadID = Thread.CurrentThread.GetHashCode();
								fileToProcess.ItemStatus = sStatus;

								if (EventfileToProcess != null)
									EventfileToProcess(this, fileToProcess);
							}
						}
					}

					if (!bAppend)
					{

						if (oDestFileLastWriteTime < oFromFileLastWriteTime)
						{
							sStatus = "New Version";
							ofileDestInfo = null;
							destFileStream = new FileStream(fileToProcess.destFile, FileMode.Create, FileAccess.Write);
							//
							fileToProcess.ItemNumber = nRow;
							fileToProcess.ThreadID = Thread.CurrentThread.GetHashCode();
							fileToProcess.ItemStatus = sStatus;

							if (EventfileToProcess != null)
								EventfileToProcess(this, fileToProcess);
						}

						else
						{
							sStatus = "Older Version";

							destFileStream = new FileStream(fileToProcess.destFile, FileMode.Append, FileAccess.Write);
							nStartOffset = (int)destFileStream.Length;
							fromFileStream.Seek(ofileDestInfo.Length, 0);
							destFileStream.Seek(ofileDestInfo.Length, 0);

							fileToProcess.ItemNumber = nRow;
							fileToProcess.ThreadID = Thread.CurrentThread.GetHashCode();
							fileToProcess.ItemStatus = sStatus;

							if (EventfileToProcess != null)
								EventfileToProcess(this, fileToProcess);

						}
					}
				}

				else
				{
					sStatus = "New File";
					ofileDestInfo = null;

					destFileStream = new FileStream(fileToProcess.destFile, FileMode.CreateNew, FileAccess.Write);
					//
					fileToProcess.ItemNumber = nRow;
					fileToProcess.ThreadID = Thread.CurrentThread.GetHashCode();
					fileToProcess.ItemStatus = sStatus;

				}

				if (fromFileStream.CanRead)
				{
					if (EventfileCreatedToProcess != null)
						EventfileCreatedToProcess(this, fileToProcess);


					decimal nOffset = 0;

					{
					
						//Set correct Chunksize depending of the Source File Size
						this.nBufferSize = GetChunkSize(fromFileStream.Length);

						//Create a buffer for passing the file contents
						Byte[] b = new Byte[this.nBufferSize];

						// calculate the modulo of the chunksize vs filesize to avoid negative value to copy
						//long nrest = fromFileStream.Length % this.nBufferSize;

						//Debug.WriteLine(" fromFileStream.Name::rest::0: " + fromFileStream.Name + " <-> " + nrest.ToString() + " <-> " +(destFileStream.Position < (fromFileStream.Length - nrest)));
						fileToProcess.BytesCopied = (long)destFileStream.Length;
						fileToProcess.StartCopyDateTime = DateTime.Now;

						while (destFileStream.Position < (fromFileStream.Length - this.nBufferSize))
						{

							fromFileStream.Read(b, 0, this.nBufferSize);
							destFileStream.Write(b, 0, b.Length);
							nbyte = (Int32)((fromFileStream.Length - destFileStream.Position));
							nOffset = (decimal)((decimal)destFileStream.Length / (decimal)fromFileStream.Length) * 100;

							//Debug.WriteLine("  !(decimal)destFileStream.Length: " + (decimal.Parse(destFileStream.Length.ToString()).ToString()));
							//Debug.WriteLine(" !!(decimal)fromFileStream.Length: " + (decimal.Parse(fromFileStream.Length.ToString()).ToString()));
							//Debug.WriteLine("!!!(decimal)destFileStream.Length / (decimal)fromFileStream.Length: " + (decimal.Parse(Convert.ToString(((decimal)destFileStream.Length / (decimal)fromFileStream.Length)*100)).ToString()));

							Monitor.Enter(stateLock);
							fileToProcess.ProcessStatus = (decimal)nOffset;
							fileToProcess.BytesCopied += (long)this.nBufferSize;
							Monitor.Exit(stateLock);

							if (EventfileProcessStatusToProcess != null)
							    EventfileProcessStatusToProcess(null, fileToProcess);

							//Debug.WriteLine(" fromFileStream.Name::rest::1:destFileStream.Position: " + ofileFromInfo.Name + " <-> " + nrest.ToString() + " <-> " + (destFileStream.Position < (fromFileStream.Length - nrest)) + " <-> " + (fromFileStream.Length - nrest) + " <-> " + destFileStream.Position.ToString());

						}

						//Check Remaining Bytes to copy 
						//Monitor.Enter(stateLock);
						nbyte = (Int32)((fromFileStream.Length - fromFileStream.Position));
						//Monitor.Exit(stateLock);

						//Debug.WriteLine(" fromFileStream.Name::nbyte::2: " + nbyte.ToString());
						if (nbyte != 0)
						{
							fromFileStream.Read(b, 0, nbyte);
							destFileStream.Write(b, 0, nbyte);
						}
						destFileStream.Flush();
					}

					//Debug.WriteLine("frmFileProcess::ProcessFile::ofileDestInfo.Exists:10");

					nbyte = (Int32)((fromFileStream.Length - fromFileStream.Position));
					nOffset = (decimal)((decimal)destFileStream.Length / (decimal)fromFileStream.Length) * 100;

					Debug.WriteLine("ProcessFile::Completing");

					fileToProcess.ItemStatus = "Completing...";
					fileToProcess.ProcessStatus = (int)nOffset;
					fileToProcess.BytesCopied = (long)destFileStream.Length;

					if (EventfileToProcess != null)
						EventfileToProcess(this, fileToProcess);

					Thread.Sleep(250);

					Debug.WriteLine("ProcessFile::Completed");
					fileToProcess.ItemStatus = "Completed";

					if (EventfileToProcess != null)
						EventfileToProcess(this, fileToProcess);

					Thread.Sleep(250);

					//Debug.WriteLine("ProcessFileCopy::ProcessFile::FileCompleted != null:" + FileCompleted != null);

					bFileCompleted = true;

					if (FileCompleted != null)
						FileCompleted(this, fileToProcess);

					//Debug.WriteLine("*FileCompleted:" + bFileCompleted + " <-> " + nbyte.ToString());

					Thread.Sleep(250);

					//return;
				}
			}

			catch (System.IO.IOException ex)
			{
				//Debug.WriteLine("ERROR::ProcessFileCopy::ProcessFile:System.IO.IOException: " + ex.Message);

				fileToProcess.ErrorStatus = ex.Message;
				if (EventfileErrorToProcess != null)
					EventfileErrorToProcess(this, fileToProcess);

			}

			catch (System.Threading.ThreadAbortException)
			{
				if (FileCompleted != null)
					FileCompleted(this, fileToProcess);

			}

			catch (System.Threading.ThreadInterruptedException ext)
			{
				if (FileCompleted != null)
					FileCompleted(this, fileToProcess);

				fileToProcess.ErrorStatus = ext.Message;
				if (EventfileErrorToProcess != null)
					EventfileErrorToProcess(this, fileToProcess);

			}

			catch (System.Exception ex)
			{
				try
				{

					Debug.WriteLine("ERROR::ProcessFileCopy::ProcessFile:System.Exception: " + ex.Message);

					fileToProcess.ErrorStatus = ex.Message;
					if (EventfileErrorToProcess != null)
						EventfileErrorToProcess(this, fileToProcess);


				}
				catch (Exception exx)
				{
					Debug.WriteLine("ERROR::ProcessFileCopy::ProcessFile:Exception " + exx.Message);
				}
			}
			finally
			{
				try
				{
					try
					{
						if (fromFileStream != null)
							fromFileStream.Close();
						if (destFileStream != null)
							destFileStream.Flush();

						destFileStream.Close();
					}
					catch (Exception)
					{ }

					try
					{
						ofileDestInfo = new FileInfo(fileToProcess.destFile);
						if (ofileDestInfo != null)
						{
							SetFileAttributes(bFileCompleted);
						}

					}
					catch (Exception)
					{ }

				}
				catch (System.Exception ex)
				{
					Debug.WriteLine("ProcessFileCopy::ProcessFile::Error: " + ex.Message);

					fileToProcess.ErrorStatus = ex.Message;
					if (EventfileErrorToProcess != null)
						EventfileErrorToProcess(this, fileToProcess);
				}
			}
		}
	}
}
